#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#define SIZE 512
#define ZERO '\0'
#define SPA " " // 分割字符串时strtok参数要是一个字符串
#define NUM 32

// 宏函数
// do{}while(0)里面是代码块，并且while(0)后面可以随意带分号
#define SkipPath(p) do{ \
    p += strlen(p)-1;\
    while(*p != '/') p--; \
}while(0)

// 获取用户名
const char* GetUsrName()
{
   const char* name = getenv("USER");
    if(name == NULL) return "None";
    return name;
}
// 获取主机名
const char* GetHostName()
{
    const char* hostname = "hcss-ecs-01ae:~";
    return hostname;
}
// 获取当前路径
const char* GetCwd()
{
    const char* cwd = getenv("PWD");
    if(cwd == NULL) return "None";
    return cwd;
}

// 获取用户的家目录
const char* GetHome()
{
    const char* home = getenv("HOME");
    if(home == NULL) return "/";
    return home;
}

// 形成一个命令行
void MakeCommandLine(char* line, size_t size)
{
    const char* usename = GetUsrName();
    const char* hostname = GetHostName();
    const char* cwd = GetCwd();

    SkipPath(cwd);

    snprintf(line, size, "%s@%s%s> ", usename, hostname, strlen(cwd) == 1 ? "/" : cwd+1);
    printf("%s", line);
    fflush(stdout);
}

int GetUseCommand(char* command, size_t n)
{
    char* s =  fgets(command, n, stdin);
    if(s == NULL) return -1;

    command[strlen(command)-1] = ZERO;
    return strlen(command);
}

char* gArgv[NUM];
void SplitCmommand(char* command, size_t n)
{
    (void)n;
    // "ls -a -l" => "ls" "-a" "-l"
    gArgv[0] = strtok(command, SPA);
    int i = 1;

    // 写成=,先赋值,再判断,如果不能再分割,strtok会返回null
    // 刚好让gArgv最后一个元素是null,并且while判断结束
    while(gArgv[i++] = strtok(NULL, SPA));
}

void Die()
{
    exit(1);
}

int lastcode = 0;
void ExecuteCommand()
{
    
    pid_t id = fork();
    if(id < 0) Die();
    else if(id == 0)
    {
        // child
        execvp(gArgv[0], gArgv);
        exit(errno);
    }
    else
    {
        // father
        int status = 0;
        pid_t rid = waitpid(id, &status, 0);
        if(rid > 0)
        {
            // 获取退出码
            lastcode = WEXITSTATUS(status);
            if(lastcode != 0)
                printf("%s:%s:%d\n",gArgv[0], strerror(lastcode), lastcode);
        }
    }
}


char cwd[SIZE*2];
void cd()
{
    const char* path = gArgv[1];
    if(path == NULL) path = GetHome();
    
     // path存在，说明cd后还有选项
     chdir(path); // 更改路径

     // 刷新环境变量
     char tmp[SIZE*2];
     getcwd(tmp, sizeof(tmp)); // 获取新的绝对路径
     snprintf(cwd, sizeof(cwd), "PWD=%s", tmp);
     putenv(cwd);
}

int CheckBuildin()
{
    int yes = 0;
    const char* EnterCmd = gArgv[0];
    if(strcmp(EnterCmd, "cd") == 0)
    {
        yes = 1;
        cd();
    }
   else if(strcmp(EnterCmd, "echo") == 0 && strcmp(gArgv[1], "$?") == 0)
    {
        yes = 1;
        printf("%d\n",lastcode);
        lastcode = 0;
    }

    return yes;
}

int main()
{
    int quit = 0;
    while(!quit)
    {
         // 1.我们需要自己输出命令行
         char CommandLine[SIZE];
         MakeCommandLine(CommandLine, sizeof(CommandLine));

         // 2.获取用户命令行字符串
         char UseCommand[SIZE];
         int n = GetUseCommand(UseCommand, sizeof(UseCommand));
         // 判断是否是命令
         if(n <= 0) return 1;
        
         // 3. 命令行字符串分割
         SplitCmommand(UseCommand, sizeof(UseCommand));
        
         // 4. 检查命令是否是内建命令--由父进程bash执行
         n = CheckBuildin();
         if(n) continue;

         // n. 执行命令
         ExecuteCommand();
    }

    return 0;
}

